package tagdb.algorithm.tag;

import com.compomics.util.experiment.biology.Enzyme;
import com.compomics.util.experiment.biology.EnzymeFactory;
import com.compomics.util.experiment.biology.Protein;
import com.compomics.util.protein.AASequenceImpl;
import org.apache.commons.collections15.MultiMap;
import org.apache.commons.collections15.multimap.MultiHashMap;
import org.xmlpull.v1.XmlPullParserException;
import tagdb.algorithm.factory.MutationFactory;
import tagdb.denovo.DenovoHit;
import tagdb.fasta.Database;
import tagdb.model.DenovoSearchResult;
import tagdb.model.ExtendedSequenceImpl;
import tagdb.model.Peptide;
import tagdb.model.SuspectHit;
import tagdb.model.params.SearchParams;
import tagdb.ui.LogPanel;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.File;
import java.io.IOException;
import java.util.*;

/**
 * <b>TagSearch</b>
 * <p>
 * This class gives the possibility to search sequence tags against a FASTA database.
 * </p>
 *
 * @author Thilo Muth
 */
public class TagSearch {

    /**
     * The uses FASTA database.
     */
    private Database fastaDB;

    /**
     * The denovo result object.
     */
    private DenovoSearchResult dnResult;

    /**
     * The logging panel
     */
    protected LogPanel logPnl;

    /**
     * The tag search parameters.
     */
    private SearchParams params;

    /**
     * PropertyChangeSupport object.
     */
    protected PropertyChangeSupport pSupport = new PropertyChangeSupport(this);

    /**
     * Old progress value.
     */
    private int oldProgress;

    /**
     * Progress value.
     */
    private int progress = 0;

    private MultiMap<String, ExtendedSequenceImpl> digestTagSeqMap;



    /**
     * Builds the tag-based search.
     *
     * @param logPnl The logging panel.
     * @param fastaDB  The FASTA database.
     * @param dnResult The denovo result object.
     * @param params   The tag serach parameters.
     */
    public TagSearch(LogPanel logPnl, Database fastaDB, DenovoSearchResult dnResult, SearchParams params) {
        this.logPnl = logPnl;
        this.fastaDB = fastaDB;
        this.dnResult = dnResult;
        this.params = params;
    }

    /**
     * TODO: Make usable for multiple proteins and a maximum of missed cleavages == 2... :)
     * @throws IOException
     * @throws InterruptedException
     */
    private void digestProteins() throws IOException, InterruptedException, XmlPullParserException {

        EnzymeFactory enzymeFactory = EnzymeFactory.getInstance();

        // Import the enzymes to the factory.
        enzymeFactory.importEnzymes(new File("resources/conf/enzymes.xml"));
        Enzyme enzyme = enzymeFactory.getEnzyme("Trypsin");

        // Init digest tag sequence map.
        digestTagSeqMap = new MultiHashMap<String, ExtendedSequenceImpl>();

        // Iterate the proteins and digest them.
        for (Protein protein : fastaDB.getProteins()) {
            ArrayList<String> cleavedSequences = enzyme.digest(protein.getSequence(), 0, 6, 30);

            // Iterate the cleaved sequences.
            for(String cleavedSeq : cleavedSequences) {
                // Extended sequence.
                ExtendedSequenceImpl extendedSequence = new ExtendedSequenceImpl(cleavedSeq);
                extendedSequence.setProtein(protein);

                // C-terminal sequence.
                String cTagSequence = cleavedSeq.substring(cleavedSeq.length()-3, cleavedSeq.length());
                digestTagSeqMap.put(cTagSequence, extendedSequence);

                // N-terminal sequence.
                String nTagSequence = cleavedSeq.substring(0, 3);
                digestTagSeqMap.put(nTagSequence, extendedSequence);
            }
        }
    }

    /**
     * Searches the derived tags from the resources-novo result against the FASTA database and returns
     * a list of potential "suspect" hits.
     *
     * @return Suspect hits.
     */
    public List<SuspectHit> searchHits() throws IOException, InterruptedException, XmlPullParserException {
        digestProteins();
        List<Tag> tags = null;

        // The suspect proteins.
        List<SuspectHit> suspects = new ArrayList<SuspectHit>();

        // Match Counter
        int matchCount = 0;

        // Mutation factory: Create mutations
        MutationFactory.createMutations();

        for (String spectrum : dnResult.getQuerySpectraNames()) {
            List<DenovoHit> hits = dnResult.getHits(spectrum);
            if (hits.size() > 0) {

                double precursorMz = dnResult.getQuerySpectrum(spectrum).getPrecursorMZ();
                int charge = dnResult.getQuerySpectrum(spectrum).getCharge();

                tags = dnResult.getTags(spectrum);
                Collections.sort(tags);
                Map<String, String> accMap = new HashMap<String, String>();
                Map<String, Integer> matchedSpectra = new HashMap<String, Integer>();

                // Iterate the tags
                for (int i = 0; i < tags.size(); i++) {
                    Tag tag = tags.get(i);

                    if(digestTagSeqMap.get(tag.getSequence()) != null) {
                        Collection<ExtendedSequenceImpl> pepSeqs = digestTagSeqMap.get(tag.getSequence());

                        // Iterate possible multiple sequences for each tag.
                        for(ExtendedSequenceImpl pepSeq: pepSeqs) {

                            // Protein protein
                            Protein protein = pepSeq.getProtein();

                            // Get the m/z for a certain charge.
                            double peptidePrecursorMz = pepSeq.getMz(charge);

                            // Get the delta of the experimental and calculated precursor mass.
                            double delta = Math.abs(peptidePrecursorMz - precursorMz);

                            //TODO: Support PPM: Util.getPpmError()

                            // Mode 1: Exact matching!
                            if (delta <= params.getPrecursorTol()) {
                                if (!accMap.containsKey(protein.getAccession())) {
                                    suspects.add(new SuspectHit(protein, spectrum, new Peptide(pepSeq.getSequence(), charge, peptidePrecursorMz), tag));
                                    matchedSpectra.put(spectrum, 1);

                                    accMap.put(protein.getAccession(), protein.getSequence());
                                    matchCount++;
                                    logPnl.append("Matched sequence: " + pepSeq.getSequence() + "\n");
                                    logPnl.append("Precursor m/z: " + peptidePrecursorMz + "\n");
                                    logPnl.append("Delta: " + delta + "\n");
                                    logPnl.append("Protein: " + protein.getAccession() + "\n\n");
                                }
                            }
                        }
                }
                }

                // Get the database
/*                List<Protein> proteins = null;
                try {
                    proteins = fastaDB.getProteins();

                    for (Protein protein : proteins) {
                        String dbSequence = protein.getSequence();

                        // Iterate the tags
                        for (int i = 0; i < tags.size(); i++) {
                            int cIndex = dbSequence.indexOf(tags.get(i).getSequence());
                            if (cIndex != -1) {
                                // Once matched: Increase the tag count of the tag in the list.
                                tags.get(i).increaseMatchCount();
                                Tag cTag = tags.get(i);

                                // Reverse the sequence
                                String revSeq = Formatter.reverse(dbSequence.substring(0, cIndex + cTag.getLength()));
                                List<Integer> endIndices = new ArrayList<Integer>();
                                // TODO: Use maximum of missed cleavages here!
                                //EnzymeFactory enzymeFactory = EnzymeFactory.getInstance();
                                //enzymeFactory.getEnzyme(Enzyme.);
                                if(revSeq.indexOf("R", 1) != -1) {  // Get the index of the next arginine
                                    endIndices.add(revSeq.indexOf("R", 1));
                                }
                                if(revSeq.indexOf("K", 1) != -1) { // Get the index of the next lysine
                                    endIndices.add(revSeq.indexOf("K", 1));
                                }
                                if (endIndices.size() > 0) {
                                    for(Integer endIndex : endIndices) {
                                        String pepSeq = Formatter.reverse(revSeq.substring(0, endIndex));
                                        double peptidePrecursorMz = Fragmentizer.calculatePrecursorMz(pepSeq, charge);
                                        *//*if(pepSeq.contains("DAQR") && charge == 2) {
                                            System.out.println("charge: " + charge);
                                            System.out.println("calc: " + peptidePrecursorMz);
                                            System.out.println("spectrum: " + precursorMz);
                                        }*//*
                                        double delta = peptidePrecursorMz- precursorMz;

                                        // Mode 1: Exact matching!
                                        if (Math.abs(delta) <= params.getPrecursorTol()) {
                                            if (!accMap.containsKey(protein.getAccession())) {
                                                suspects.add(new SuspectHit(protein, spectrum, new Peptide(pepSeq, charge, peptidePrecursorMz), cTag));
                                                matchedSpectra.put(spectrum, 1);

                                                accMap.put(protein.getAccession(), protein.getSequence());
                                                matchCount++;
                                                logPnl.append("Matched sequence: " + pepSeq + "\n");
                                                logPnl.append("Precursor m/z: " + peptidePrecursorMz + "\n");
                                                logPnl.append("Delta: " + Math.abs(delta) + "\n");
                                                logPnl.append("Protein: " + protein.getAccession() + "\n\n");
                                            }
                                        } *//*else if(delta <= 116 && !matchedSpectra.containsKey(denovoEntry.getSpectrumName())) {
                                            // Mode 2: Mutation tolerant matching
                                            // Check if mutation mass shift is similar to the delta

                                            Mutation mut = MutationFactory.getMutation(new Mutation(AminoAcid.E, AminoAcid.D));

                                            if(Math.abs(delta - mut.getMassShift()) <= params.getPrecursorTol()) {
                                                if (!accMap.containsKey(protein.getAccession())) {
                                                    suspects.add(new SuspectHit(protein, denovoEntry.getSpectrumName(), new Peptide(pepSeq, charge, peptidePrecursorMz), cTag));
                                                    System.out.println(protein.getSequence());
                                                    accMap.put(protein.getAccession(), protein.getSequence());
                                                    System.out.println("Mutation found - mass shift:" + mut.getMassShift());
                                                    logPnl.append("Mutation Found: " + pepSeq + "\n");
                                                    logPnl.append("Precursor m/z: " + peptidePrecursorMz + "\n");
                                                    logPnl.append("Protein: " + protein.getAccession() + "\n\n");
                                                }
                                            }
                                        } *//*
                                    }
                                }
                            }
                        }
                    }
                } catch (IOException e) {
                    e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
                } catch (InterruptedException e) {
                    e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
                }*/
            }

            oldProgress = progress;
            progress++;
            pSupport.firePropertyChange("progress", progress, oldProgress);
        }
        logPnl.append("Total match count: " + matchCount);
        return suspects;
    }

    /**
     * Adds the property change listener.
     *
     * @param l
     */
    public void addPropertyChangeListener(PropertyChangeListener l) {
        pSupport.addPropertyChangeListener(l);
    }

}
